package joe.works.pluginframework.extensionpoints;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

import joe.works.pluginframework.IExtensionPoint;
import joe.works.pluginframework.IPlugin;
import joe.works.pluginframework.plugins.StrategyPlugin;

/**
 * StrategyExtention point defines an extension point for a pluggalbe strategy  
 * @author josseyj
 *
 * @param <Type>
 */
public class StrategyExtensionPoint<Type> implements IExtensionPoint {
	private String strategyName;
	private Class<Type> strategyType;
	private Object target;
	private boolean useDummy;
	private boolean useCompositeStrategy;
	private InvocationType invocationType = InvocationType.NONE;

	public String getName() {
		return getClass().getName();
	}
	
	public void setStrategyType(Class<Type> strategyType) {
		if(!strategyType.isInterface()) {
			throw new IllegalArgumentException("Strategy type muct be an interface");
		}
		this.strategyType = strategyType;
	}
	
	public void setTarget(Object target) {
		this.target = target;
	}
	
	public void setUseCompositeStrategy(boolean useCompositeStrategy) {
		this.useCompositeStrategy = useCompositeStrategy;
	}
	
	public void setUseDummy(boolean useDummy) {
		this.useDummy = useDummy;
	}

	public Class<? extends IPlugin> getPluginType() {
		return StrategyPlugin.class;
	}

	public boolean isPlugable(IPlugin plugin) {
		if(plugin instanceof StrategyPlugin) {
			StrategyPlugin<Type> strategyPlugin = (StrategyPlugin<Type>) plugin;
			return 
				strategyName.equals(strategyPlugin.getStrategyName()) && 
				strategyType.isInstance(strategyPlugin.getStrategy());
		}
		return false;
	}

	public void loadPlugins(List<IPlugin> plugins) {
		Type strategy = null;
		if(plugins.size() == 0) {
			if(useDummy) {
				strategy = (Type) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{strategyType}, new DummyInvocationHandler());
			}
		} else if(plugins.size() > 1) {
			if(!useCompositeStrategy) {
				throw new IllegalStateException("Only one startegy is supported.");
			}
			List<Type> strategies = new ArrayList<Type>();
			for(IPlugin plugin : plugins) {
				StrategyPlugin<Type> strategyPlugin = (StrategyPlugin<Type>) plugin;
				strategies.add(strategyPlugin.getStrategy());
			}
			strategy = (Type) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{strategyType}, new CompositeInvocationHandler<Type>(strategies, invocationType));
		} else {
			StrategyPlugin<Type> strategyPlugin = (StrategyPlugin<Type>) plugins.get(0);
			strategy = strategyPlugin.getStrategy();
		}
		try {
			Field field = target.getClass().getDeclaredField(strategyName);
			field.setAccessible(true);
			field.set(target, strategy);
		} catch (Exception ex) {
			throw new IllegalStateException(ex.getMessage(), ex);
		}
	}

	public void setStrategyName(String strategyName) {
		this.strategyName = strategyName;
	}
	
	private class DummyInvocationHandler implements InvocationHandler {

		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			return null;
		}
		
	}
	
	private class CompositeInvocationHandler<Type> implements InvocationHandler {
		private List<Type> delegates;
		private InvocationType invocationType;
		
		public CompositeInvocationHandler(List<Type> delegates, InvocationType invocationType) {
			this.delegates = delegates;
			this.invocationType = invocationType;
		}

		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			if(delegates != null) {
				for(Type delegate: delegates) {
					method.invoke(delegate, args);
				}
			}
			return null;
		}
		
	}

}
